package ua.lviv.franko.solvers.stressanalysis.twoDimention.fem;

import java.util.List;

import ua.lviv.franko.integration.Gauss2D;
import ua.lviv.franko.integration.IFunction2D;
import ua.lviv.franko.integration.MatrixFunction2D;
import ua.lviv.franko.integration.VectorFunction2D;
import ua.lviv.franko.solvers.stressanalysis.twoDimention.ElementMatrixForStress;
import ua.lviv.franko.triangulation.SaveStructures.Node;

public abstract class AbstractIsoparametricQuadrilateral extends AbstractStressElement {

	public static final int				QuadraturePointsNumber	= 7;
	protected static final double		bottom					= -1.0;
	protected static final double		left					= -1.0;
	protected static final double		right					= 1.0;
	protected static final double		top						= 1.0;
	protected static final Gauss2D		gau;

	protected ElementMatrixForStress	data;
	protected List<Node>				nodes;

	protected List<Integer>				pos;

	static {
		gau = new Gauss2D(QuadraturePointsNumber);
	}

	public AbstractIsoparametricQuadrilateral(double k1, double k2, double k3, IFunction2D f1Function, IFunction2D f2Function) {
		super(k1, k2, k3, f1Function, f2Function);
	}

	@Override
	public double calculateU(double x, double y, double[] vals) {
		return 0;
	}

	@Override
	public double calculateV(double x, double y, double[] vals) {
		return 0;
	}

	@Override
	public ElementMatrixForStress getAll() {
		return data;
	}

	@Override
	public List<Integer> getPos() {
		return pos;
	}

	@Override
	protected ElementMatrixForStress calculateSmallData(int i, int j) {
		ElementMatrixForStress res = new ElementMatrixForStress(getNodesNumbers());
		res.matrix = this.calculateSmallMatrix(i, j);
		res.vector = this.calculateSmallVector(i);
		return res;
	}

	@Override
	protected double[][] calculateSmallMatrix(int i, int j) {
		final IFunction2D fi = baseFunctions.get(i);
		final IFunction2D fj = baseFunctions.get(j);
		return gau.integrate(left, right, bottom, top, new MatrixFunction2D() {

			@Override
			public double[][] calculate(double x, double y) {
				double xDerFi = fi.calculateDerivatyX(x, y);
				double yDerFi = fi.calculateDerivatyY(x, y);
				double xDerFj = fj.calculateDerivatyX(x, y);
				double yDerFj = fj.calculateDerivatyY(x, y);

				double jac = determinant2x2Matrix(jacobian(x, y), x, y);
				double[][] invertedJac = invertedJacobian(x, y);
				double xDerFiReal = xDerFi * invertedJac[0][0] + yDerFi * invertedJac[0][1];
				double yDerFiReal = xDerFi * invertedJac[1][0] + yDerFi * invertedJac[1][1];
				double xDerFjReal = xDerFj * invertedJac[0][0] + yDerFj * invertedJac[0][1];
				double yDerFjReal = xDerFj * invertedJac[1][0] + yDerFj * invertedJac[1][1];
				// not needed as k* a constant coefs
				// double realX = getRealX(x, y);
				// double realY = getRealY(x, y);

				return new double[][] {
						{ (xDerFiReal * xDerFjReal * k1 + yDerFiReal * yDerFjReal * k3) * jac, (xDerFjReal * yDerFiReal * k2 + yDerFjReal * xDerFiReal * k3) * jac },
						{ (xDerFjReal * yDerFiReal * k2 + yDerFjReal * xDerFiReal * k3) * jac, (xDerFiReal * xDerFjReal * k3 + yDerFiReal * yDerFjReal * k1) * jac } };
			}

			@Override
			public double[][] calculateDerivatyX(double x, double y) {
				return null;
			}

			@Override
			public double[][] calculateDerivatyY(double x, double y) {
				return null;
			}

			@Override
			public int matrixSize() {
				return 2;
			}
		});
	}

	@Override
	protected double[] calculateSmallVector(int i) {
		final IFunction2D fi = baseFunctions.get(i);

		return gau.integrate(left, right, bottom, top, new VectorFunction2D() {

			@Override
			public double[] calculate(double x, double y) {
				double fiValue = fi.calculate(x, y);
				double jac = determinant2x2Matrix(jacobian(x, y), x, y);
				double realX = getRealX(x, y);
				double realY = getRealY(x, y);

				return new double[] { fiValue * f1Function.calculate(realX, realY) * jac, fiValue * f2Function.calculate(realX, realY) * jac };
			}

			@Override
			public double[] calculateDerivatyX(double x, double y) {
				return null;
			}

			@Override
			public double[] calculateDerivatyY(double x, double y) {
				return null;
			}

			@Override
			public int vectorSize() {
				return 2;
			}
		});
	}

	@Override
	public List<Integer> getNodesNumbers() {
		return getPos();
	}

	/**
	 * calculates a real value of x from local x y ....
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	protected double getRealX(double x, double y) {
		double res = 0.0;
		for (int i = 0; i < nodes.size(); i++) {
			res += nodes.get(i).x * baseFunctions.get(i).calculate(x, y);
		}
		return res;
	}

	/**
	 * calculates a real value of y from local x y ....
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	protected double getRealY(double x, double y) {
		double res = 0.0;
		for (int i = 0; i < nodes.size(); i++) {
			res += nodes.get(i).y * baseFunctions.get(i).calculate(x, y);
		}
		return res;
	}

	protected double[][] invertedJacobian(double x, double y) {
		double[][] jac = jacobian(x, y);
		double det = jac[0][0] * jac[1][1] - jac[0][1] * jac[1][0];
		return new double[][] { { jac[1][1] / det, -jac[0][1] / det }, { -jac[1][0] / det, jac[0][0] / det } };
	}

	protected double[][] jacobian(double x, double y) {
		double[][] res = new double[2][2];

		for (int i = 0; i < baseFunctions.size(); i++) {
			IFunction2D func = baseFunctions.get(i);
			double xDer = func.calculateDerivatyX(x, y);
			double yDer = func.calculateDerivatyY(x, y);
			res[0][0] += xDer * nodes.get(i).x;
			res[0][1] += xDer * nodes.get(i).y;
			res[1][0] += yDer * nodes.get(i).x;
			res[1][1] += yDer * nodes.get(i).y;
		}

		return res;
	}

	protected static double determinant2x2Matrix(double[][] jac, double x, double y) {
		return jac[0][0] * jac[1][1] - jac[0][1] * jac[1][0];
	}

}
